iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
自我挑戰組

玩轉 React 從0到1系列 第 4

【Day 4】關於ES6原型與原型鏈

  • 分享至 

  • xImage
  •  

前言

If you don't understand prototypes, you don't understand Javascript.
如果你沒搞懂原型,你就不算懂 Javascript.

Javascript 與其他語言最大的不同是它沒有像 Java, C++ 一樣有 class (類)的概念,如果有了class的概念,勢必會增加像是 抽象(abstract), 多型(polymorphism), 封裝 (encapsulation) 等原生基礎概念,會提高學習難度(雖然可以後續實作),但 Javascript 還是需要有繼承的方法,那它是透過原型 (Prototype) 機制實現相互繼承功能。

舉例來說:

function Cat(name) {
    this.name = name;
    this.species = '貓科';
    this.log = function() {
        console.log('name' + this.name + 'species' + this.species);
    }
}
var ScottishFold = new Cat('Scottish Fold');
var Lion = new Cat('Lion');
console.log(ScottishFold.name);  // ScottishFold
console.log(Lion.name);   // Lion
console.log(ScottishFold.log === Lion.log);  // false

以這裡來說 Cat 就是一個構造函數 (constructor),表示對象 ScottishFold 和 Lion 的原型(Prototype),而利用構造函數就可以創造出一個 Cat 的實例(instance)。
但也因為是用 new ,所以 ScottishFold 和 Lion 都是獨立的,各佔據記憶體,所以 ScottishFold.log != Lion.log,這也是 new 的缺點,也因此有了原型鏈(prototype chain)的產生。

原型

在構造函數創建的時候,系統默認的幫構造函數創建關聯一個對象,這個對象就是原型

在原型中的所有屬性跟方法,都可以被和其關聯的構造函數創建出來的所有對象共享

function Cat(name) {
	this.name = name;
}
Cat.prototype.playWithMe = function() {
	console.log(this.name+' play with me now');
}
var ScottishFold = new Cat('Scottish Fold');
ScottishFold.playWithMe(); // Scottish Fold play with me now
ScottishFold.name = 'White Scottish Fold';
ScottishFold.playWithMe();     // White Scottish Fold play with me now
console.log(ScottishFold.__proto__ == Cat.prototype);   //true
console.log(ScottishFold.__proto__.__proto__  ==  Object.prototype);   //true
console.log(Cat.prototype);
console.log(ScottishFold);

https://ithelp.ithome.com.tw/upload/images/20200920/201099630PhdFQIp8x.png

在這裡要解釋一下,__proto__prototype的不同:

  1. __proto__ 是每個對象都會有的一個屬性,prototype是函數(function)才會有的屬性
  2. __proto__ 指向的是當前對象的原型對象,prototype指向的是當前原始構造函數的原型對象

所以這裡要注意,Cat.prototype 不是 Cat 原型,而是構造函數執行後建立的新物件原型。
(請參照console.log(Cat.prototype)裡的 __proto__ 是 Object唷)

以下為流程圖:
https://ithelp.ithome.com.tw/upload/images/20200920/20109963IO6ub4ieHY.jpg

問題:

  1. ScottishFold.prototype 會是? 答案 undefined,因為它不是函數。

原型鏈

每個對象都有原型,原型本身又是一個原型,通過__proto__向上指,最後會先變成 Object,在往上就變成 null,一個對象通過原型鏈會擁有定義在其他對象中的屬性和方法

function Cat(name) {
	this.name = name;
}

var ScottishFold = new Cat('Scottish Fold');
console.log(ScottishFold);   // Cat{name: 'Scottish Fold'}
console.log(ScottishFold.__proto__ === Cat.prototype);   // true
console.log(ScottishFold.__proto__.__proto__ === Object.prototype);  //true
console.log(ScottishFold.__proto__.__proto__.__proto__ === null);    //true

ScottishFold 雖然沒有 constructor 屬性,但會通過原型鏈向上找 __proto__,最終找到指向 Cat.prototype。

如何實作? 實現__proto__

Object.defineProperty(Object.prototype,'__proto__',{
  get: function(){
    return Object.getPrototypeOf(this)  // 獲取對象的[[Prototype]]
  },
  set: function(o){
    Object.setPrototypeOf(this,o) // 設置對象的[[Prototype]]關聯的原型為o
    return o
  }
})

其中 [[Prototype]]是一個設定 Object 的 Prototype 的接口,是一個隱藏屬性,屬性指向對象的原型,其實也可以理解為瀏覽器顯示的__proto__屬性,在 ES5 透過 getPrototypeOf 取得,透過 setPrototypeOf 修改。

結論

  • 介紹了 原型 與 原型鏈

/images/emoticon/emoticon04.gifQQ不小心按到重整又忘記存了

參考文件

Chapter 17. Objects and Inheritance


上一篇
【Day 3】關於ES6展開/其餘運算符與物件拷貝
下一篇
【Day 5】關於ES6從類別到繼承
系列文
玩轉 React 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言